Completed
Push — master ( 317dbe...83670a )
by Rafael S.
03:21
created

WaveFileCreator.createADPCMHeader_   A

Complexity

Conditions 1

Size

Total Lines 16
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 16
rs 9.7
c 0
b 0
f 0
cc 1
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileCreator class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import WaveFileParser from './wavefile-parser';
33
import interleave from './interleave';
34
import dwChannelMask from './dw-channel-mask';
35
import {packArrayTo} from 'byte-data';
36
37
/**
38
 * A class to read, write and create wav files.
39
 * @extends WaveFileParser
40
 */
41
export default class WaveFileCreator extends WaveFileParser {
42
43
  /**
44
   * Set up the WaveFileCreator object based on the arguments passed.
45
   * @param {number} numChannels The number of channels
46
   *    (Integer numbers: 1 for mono, 2 stereo and so on).
47
   * @param {number} sampleRate The sample rate.
48
   *    Integer numbers like 8000, 44100, 48000, 96000, 192000.
49
   * @param {string} bitDepthCode The audio bit depth code.
50
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
51
   *    or any value between '8' and '32' (like '12').
52
   * @param {!Array<number>|!Array<!Array<number>>|!TypedArray} samples
53
   *    The samples. Must be in the correct range according to the bit depth.
54
   * @param {?Object} options Optional. Used to force the container
55
   *    as RIFX with {'container': 'RIFX'}
56
   * @throws {Error} If any argument does not meet the criteria.
57
   */
58
  fromScratch(numChannels, sampleRate, bitDepthCode, samples, options={}) {
59
    if (!options.container) {
60
      options.container = 'RIFF';
61
    }
62
    this.container = options.container;
63
    this.bitDepth = bitDepthCode;
64
    samples = interleave(samples);
65
    this.updateDataType();
66
    /** @type {number} */
67
    let numBytes = this.dataType.bits / 8;
68
    this.data.samples = new Uint8Array(samples.length * numBytes);
69
    packArrayTo(samples, this.dataType, this.data.samples);
70
    this.clearHeader();
71
    this.makeWavHeader_(
72
      bitDepthCode, numChannels, sampleRate,
73
      numBytes, this.data.samples.length, options);
74
    this.data.chunkId = 'data';
75
    this.data.chunkSize = this.data.samples.length;
76
    this.validateWavHeader();
77
  }
78
79
  /**
80
   * Define the header of a wav file.
81
   * @param {string} bitDepthCode The audio bit depth
82
   * @param {number} numChannels The number of channels
83
   * @param {number} sampleRate The sample rate.
84
   * @param {number} numBytes The number of bytes each sample use.
85
   * @param {number} samplesLength The length of the samples in bytes.
86
   * @param {!Object} options The extra options, like container defintion.
87
   * @private
88
   */
89
  makeWavHeader_(
90
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
91
    if (bitDepthCode == '4') {
92
      this.createADPCMHeader_(
93
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
94
95
    } else if (bitDepthCode == '8a' || bitDepthCode == '8m') {
96
      this.createALawMulawHeader_(
97
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
98
99
    } else if(Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
100
        numChannels > 2) {
101
      this.createExtensibleHeader_(
102
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
103
104
    } else {
105
      this.createPCMHeader_(
106
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);      
107
    }
108
  }
109
110
  /**
111
   * Create the header of a linear PCM wave file.
112
   * @param {string} bitDepthCode The audio bit depth
113
   * @param {number} numChannels The number of channels
114
   * @param {number} sampleRate The sample rate.
115
   * @param {number} numBytes The number of bytes each sample use.
116
   * @param {number} samplesLength The length of the samples in bytes.
117
   * @param {!Object} options The extra options, like container defintion.
118
   * @private
119
   */
120
  createPCMHeader_(
121
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
122
    this.container = options.container;
123
    this.chunkSize = 36 + samplesLength;
124
    this.format = 'WAVE';
125
    this.bitDepth = bitDepthCode;
126
    this.fmt = {
127
      chunkId: 'fmt ',
128
      chunkSize: 16,
129
      audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534,
130
      numChannels: numChannels,
131
      sampleRate: sampleRate,
132
      byteRate: (numChannels * numBytes) * sampleRate,
133
      blockAlign: numChannels * numBytes,
134
      bitsPerSample: parseInt(bitDepthCode, 10),
135
      cbSize: 0,
136
      validBitsPerSample: 0,
137
      dwChannelMask: 0,
138
      subformat: []
139
    };
140
  }
141
142
  /**
143
   * Create the header of a ADPCM wave file.
144
   * @param {string} bitDepthCode The audio bit depth
145
   * @param {number} numChannels The number of channels
146
   * @param {number} sampleRate The sample rate.
147
   * @param {number} numBytes The number of bytes each sample use.
148
   * @param {number} samplesLength The length of the samples in bytes.
149
   * @param {!Object} options The extra options, like container defintion.
150
   * @private
151
   */
152
  createADPCMHeader_(
153
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
154
    this.createPCMHeader_(
155
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
156
    this.chunkSize = 40 + samplesLength;
157
    this.fmt.chunkSize = 20;
158
    this.fmt.byteRate = 4055;
159
    this.fmt.blockAlign = 256;
160
    this.fmt.bitsPerSample = 4;
161
    this.fmt.cbSize = 2;
162
    this.fmt.validBitsPerSample = 505;
163
    this.fact = {
164
      chunkId: 'fact',
165
      chunkSize: 4,
166
      dwSampleLength: samplesLength * 2
167
    };
168
  }
169
170
  /**
171
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
172
   * @param {string} bitDepthCode The audio bit depth
173
   * @param {number} numChannels The number of channels
174
   * @param {number} sampleRate The sample rate.
175
   * @param {number} numBytes The number of bytes each sample use.
176
   * @param {number} samplesLength The length of the samples in bytes.
177
   * @param {!Object} options The extra options, like container defintion.
178
   * @private
179
   */
180
  createExtensibleHeader_(
181
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
182
    this.createPCMHeader_(
183
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
184
    this.chunkSize = 36 + 24 + samplesLength;
185
    this.fmt.chunkSize = 40;
186
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
187
    this.fmt.cbSize = 22;
188
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
189
    this.fmt.dwChannelMask = dwChannelMask(numChannels);
190
    // subformat 128-bit GUID as 4 32-bit values
191
    // only supports uncompressed integer PCM samples
192
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
193
  }
194
195
  /**
196
   * Create the header of mu-Law and A-Law wave files.
197
   * @param {string} bitDepthCode The audio bit depth
198
   * @param {number} numChannels The number of channels
199
   * @param {number} sampleRate The sample rate.
200
   * @param {number} numBytes The number of bytes each sample use.
201
   * @param {number} samplesLength The length of the samples in bytes.
202
   * @param {!Object} options The extra options, like container defintion.
203
   * @private
204
   */
205
  createALawMulawHeader_(
206
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
207
    this.createPCMHeader_(
208
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
209
    this.chunkSize = 40 + samplesLength;
210
    this.fmt.chunkSize = 20;
211
    this.fmt.cbSize = 2;
212
    this.fmt.validBitsPerSample = 8;
213
    this.fact = {
214
      chunkId: 'fact',
215
      chunkSize: 4,
216
      dwSampleLength: samplesLength
217
    };
218
  }
219
}
220